package com.xuhuan.client.business.splitfile.service.impl;

import com.xuhuan.client.business.splitfile.dto.SplitFileResult;
import com.xuhuan.client.business.splitfile.dto.ZoneInfo;
import com.xuhuan.client.business.splitfile.entity.FileSplitDetail;
import com.xuhuan.client.business.splitfile.entity.FileSplitLog;
import com.xuhuan.client.business.splitfile.feign.ISplitFileRemoteClient;
import com.xuhuan.client.business.splitfile.properties.FileSplitProperties;
import com.xuhuan.client.business.splitfile.service.IFileSplitDetailService;
import com.xuhuan.client.business.splitfile.service.IFileSplitFacadeService;
import com.xuhuan.client.business.splitfile.service.IFileSplitLogService;
import com.xuhuan.client.business.splitfile.vo.ZoneMd5ListVO;
import com.xuhuan.client.business.splitfile.vo.SaveFileSplitLogVO;
import com.xuhuan.client.business.splitfile.vo.ZoneCheckRequestVO;
import com.xuhuan.client.business.splitfile.vo.ZoneUploadVO;
import com.xuhuan.client.result.RequestResult;
import com.xuhuan.client.util.FileHandleUtil;
import com.xuhuan.client.util.FileSplitUtill;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * @Author huan.xu
 * @Date 2023/6/17 9:28
 * @Description
 */
@Slf4j
@Service
public class FileSplitFacadeServiceImpl implements IFileSplitFacadeService {

    @Autowired
    private FileSplitProperties fileSplitProperties;
    @Autowired
    private ISplitFileRemoteClient splitFileRemoteClient;
    @Autowired
    private IFileSplitLogService fileSplitLogService;
    @Autowired
    private IFileSplitDetailService fileSplitDetailService;

    /**
     * 获取参数配置
     *
     * @return
     */
    @Override
    public FileSplitProperties getFileSplitProperties() {
        return fileSplitProperties;
    }

    /**
     * 文件分片
     *
     * @param file
     * @return
     */
    @Override
    public SplitFileResult splitFile(File file) {
        SplitFileResult result = null;
        try {
            result = FileSplitUtill.splitFile(file.getPath()
                    , fileSplitProperties.getTempBasePath(), fileSplitProperties.getPartitionSize());
        } catch (Exception e) {
            log.error("文件切片失败：{}", ExceptionUtils.getFullStackTrace(e));
            throw new RuntimeException("文件切片失败");
        }
        return result;
    }

    /**
     * 保存附件分片日志
     *
     * @param saveFileSplitLogVO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public RequestResult saveFileSplitLog(SaveFileSplitLogVO saveFileSplitLogVO) {
        String filePath = saveFileSplitLogVO.getFilePath();
        if (StringUtils.isBlank(filePath)) {
            throw new RuntimeException("附件路径为空");
        }
        File file = new File(filePath);
        if (!file.exists()) {
            throw new RuntimeException("附件路径不正确");
        }
        if (!file.isFile()) {
            throw new RuntimeException("需要上传文件，而不是文件夹");
        }

        String stopWatchTitle = "保存附件分片日志";
        StopWatch stopWatch = new StopWatch(stopWatchTitle);
        stopWatch.start("附件进行分片");
        //附件进行分片
        SplitFileResult splitFileResult = this.splitFile(file);
        log.info("分片结果：{}", splitFileResult);

        if (splitFileResult == null) {
            throw new RuntimeException("附件分片失败");
        }
        List<String> zoneMd5List = splitFileResult.getZoneInfoList().stream()
                .map(zoneInfo -> zoneInfo.getZoneMd5())
                .collect(Collectors.toList());
        //通过分片md5值计算的文件md5值，作为文件的唯一标识
        String fileMd5=splitFileResult.getFileMd5();
        stopWatch.stop();

        stopWatch.start("检查分片是否已上传");
        //检查分片是否已上传
        ZoneCheckRequestVO zoneCheckRequestVO = new ZoneCheckRequestVO();
        zoneCheckRequestVO.setFileMd5(fileMd5);
        RequestResult<ZoneMd5ListVO> checkRequestResult = splitFileRemoteClient.checkZoneFile(zoneCheckRequestVO);
        if (RequestResult.FAIL_CODE.equals(checkRequestResult.getResultcode())) {
            throw new RuntimeException("服务端检查分片是否已上传失败");
        }

        //得到已上传的数据
        List<String> uploadedZoneMd5List = checkRequestResult.getData().getZoneMd5List();
        String uploadFileMd5 = checkRequestResult.getData().getFileMd5();
        //得到还需要上传的分片
        List<String> neetUploadMd5List = zoneMd5List.stream()
                .filter(m -> !uploadedZoneMd5List.contains(m))
                .collect(Collectors.toList());

        //已上传过，并且需要再上传的分片为空，这种情况就是秒传
        if(StringUtils.isNotBlank(uploadFileMd5)&& CollectionUtils.isEmpty(neetUploadMd5List)){
            //已存在相同文件，提示秒传，需要调用接口告诉协会，这里附件秒传了，需要更新关系 todo
            return RequestResult.success("文件已秒传");
        }

        stopWatch.stop();

        stopWatch.start("保存分片信息");
        //保存分片信息
        FileSplitLog fileSplitLog = fileSplitLogService.getByFileMd5(fileMd5);
        if (fileSplitLog == null) {
            fileSplitLog = new FileSplitLog();
            fileSplitLog.setStorepath(file.getPath());
            fileSplitLog.setName(file.getName());
            fileSplitLog.setFiletype(FileHandleUtil.getFileExName(file.getName()));
            fileSplitLogService.saveOrUpdate(fileSplitLog);
            Long fileSplitLogId = fileSplitLog.getId();

            List<FileSplitDetail> fileSplitDetailList = splitFileResult.getZoneInfoList().stream()
                    .map(zoneInfo -> {
                        FileSplitDetail fileSplitDetail = new FileSplitDetail();
                        fileSplitDetail.setLogId(fileSplitLogId);
                        fileSplitDetail.setZonePath(zoneInfo.getPath());
                        fileSplitDetail.setZoneName(zoneInfo.getName());
                        fileSplitDetail.setZoneMd5(zoneInfo.getZoneMd5());
                        return fileSplitDetail;
                    })
                    .collect(Collectors.toList());
            fileSplitDetailService.saveBatch(fileSplitDetailList);
        }
        stopWatch.stop();

        stopWatch.start("发送分片数据给协会");
        //发送分片数据给协会
        //发送需要续传的分片数据给协会
        List<Future<String>> receiveSignZoneFileFutureList = new ArrayList<>();
        splitFileResult.getZoneInfoList().stream()
                .filter(zoneInfo -> neetUploadMd5List.contains(zoneInfo.getZoneMd5()))
                .forEach(zoneInfo -> {
                    Future<String> future = this.receiveSignZoneFile(zoneInfo,fileMd5, splitFileResult.getZoneProfixName());
                    receiveSignZoneFileFutureList.add(future);
                });
        //等待全部完成
        boolean errorReceiveSignZoneResult = receiveSignZoneFileFutureList.stream()
                .map(future -> {
                    try {
                        return future.get();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (ExecutionException e) {
                        throw new RuntimeException(e);
                    }
                })
                .anyMatch(requestResult -> RequestResult.FAIL_CODE.equals(requestResult));

        if (errorReceiveSignZoneResult) {
            log.error("发送文件分片信息到协会失败");
            throw new RuntimeException("发送文件分片信息到协会失败");
        }
        stopWatch.stop();

        stopWatch.start("发送合并分片的请求");
        //分片发送完成，发送合并分片的请求
        ZoneMd5ListVO zoneMd5ListVO = new ZoneMd5ListVO();
        zoneMd5ListVO.setZoneMd5List(zoneMd5List);
        zoneMd5ListVO.setFileFullName(file.getName());
        zoneMd5ListVO.setFileMd5(fileMd5);
        RequestResult mergeZoneFileResult = splitFileRemoteClient.mergeZoneFile(zoneMd5ListVO);
        if (RequestResult.FAIL_CODE.equals(mergeZoneFileResult.getResultcode())) {
            throw new RuntimeException("服务端合并分片失败");
        }
        stopWatch.stop();
        log.info(stopWatch.prettyPrint());
        log.info("总耗时:{}秒", stopWatch.getTotalTimeSeconds());

        return RequestResult.success();
    }

    /**
     * 异步给协会端发送分片文件
     *
     * @param zoneInfo
     * @param fileMd5
     * @param zoneProfixName
     * @return
     */
    @Async("fileSplitAsyncThreadPool")
    @Override
    public Future<String> receiveSignZoneFile(ZoneInfo zoneInfo,String fileMd5, String zoneProfixName) {
        try {
            ZoneUploadVO zoneUploadVO = new ZoneUploadVO();
            zoneUploadVO.setFileFullName(zoneInfo.getName());
            zoneUploadVO.setZoneMd5(zoneInfo.getZoneMd5());
            zoneUploadVO.setFileContent(FileUtils.readFileToByteArray(new File(zoneInfo.getPath())));
            zoneUploadVO.setZoneProfixName(zoneProfixName);
            zoneUploadVO.setFileMd5(fileMd5);
            //这里调用服务端接口发送数据
            RequestResult receiveSignZoneFileResult = splitFileRemoteClient.receiveSignZoneFile(zoneUploadVO);
            if (RequestResult.FAIL_CODE.equals(receiveSignZoneFileResult.getResultcode())) {
                log.error("发送文件分片信息到协会失败");
                return AsyncResult.forValue(RequestResult.FAIL_CODE);
            }
            return AsyncResult.forValue(RequestResult.SUCCESS_CODE);
        } catch (Exception e) {
            log.error("发送文件分片信息到协会失败:{}", ExceptionUtils.getFullStackTrace(e));
            throw new RuntimeException("发送文件分片信息到协会失败");
        }
    }

}
